package top.frsf.autocodegen.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import top.frsf.autocodegen.controller.dto.CodeGenDTO;
import top.frsf.autocodegen.entity.AutoField;
import top.frsf.autocodegen.entity.AutoTable;
import top.frsf.autocodegen.meta.RootFileItem;
import top.frsf.autocodegen.service.IAutoFieldService;
import top.frsf.autocodegen.service.IAutoTableService;
import top.frsf.autocodegen.service.ICodeGeneratorService;
import top.frsf.autocodegen.utils.EnvConfigUtil;

import java.io.*;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @author FRSF
 * @date 2024/7/31 22:11
 * @desc CodeGenServiceImpl
 */
@Service
public class CodeGeneratorServiceImpl implements ICodeGeneratorService {

    private static final Logger log = LoggerFactory.getLogger(CodeGeneratorServiceImpl.class);
    @Resource
    private FreeMarkerConfigurer freeMarkerConfigurer;
    @Resource
    private IAutoTableService autoTableService;
    @Resource
    private IAutoFieldService autoFieldService;


    @Override
    public void genCodeOnDesktop(CodeGenDTO dto) {
        // 读取数据模型
        Map<String, Object> dataModel = getDataModel(dto.getTableId());
        String className = dataModel.get("ClassName").toString();
        String desktopPath = EnvConfigUtil.getUserDesktopPath();

        // bfs模板文件
        List<RootFileItem> items = bfsRootFile(dto.getModelName());

        // CodeGen
        items.forEach(item -> {
            String outputDir = desktopPath + className + "/" + item.getOutputDir();
            File file = new File(outputDir);
            if (!file.exists()) {
                boolean mkdirs = file.mkdirs();
            }
            generateJavaFile(dataModel, item.getTemplatePath(),
                    outputDir
                            + className
                            + item.getOutputSuffix()
            );
        });
    }

    @Override
    public ResponseEntity<byte[]> genCodeOnBrowser(CodeGenDTO dto) throws IOException, TemplateException {
        // 读取数据模型
        Map<String, Object> dataModel = getDataModel(dto.getTableId());
        String className = dataModel.get("ClassName").toString();

        // bfs模板文件
        List<RootFileItem> templates = bfsRootFile(dto.getModelName());

        // 创建输出流
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ZipOutputStream zip = new ZipOutputStream(baos);
        Configuration cfg = freeMarkerConfigurer.getConfiguration();

        for (RootFileItem templateOne : templates) {
            Template template = cfg.getTemplate(templateOne.getTemplatePath());
            StringWriter writer = new StringWriter();
            template.process(dataModel, writer);

            // 添加到zip
            ZipEntry zipEntry = new ZipEntry(templateOne.getOutputDir() + className + templateOne.getOutputSuffix());
            zip.putNextEntry(zipEntry);
            zip.write(writer.toString().getBytes());
            zip.closeEntry();
        }
        zip.close();

        // 设置HTTP响应头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        headers.setContentDispositionFormData("attachment", className + ".zip");
        headers.setAccessControlExposeHeaders(List.of("Content-Disposition"));

        System.out.println(headers);

        return ResponseEntity.ok()
                .headers(headers)
                .body(baos.toByteArray());
    }

    @Override
    public void generateJavaFile(Map<String, Object> dataModel, String templateName, String outputPath) {
        try {
            Configuration config = freeMarkerConfigurer.getConfiguration();
            Template template = config.getTemplate(templateName, "UTF-8");

            File outputFile = new File(outputPath);
            try (FileWriter writer = new FileWriter(outputFile)) {
                template.process(dataModel, writer);
            }
        } catch (IOException | TemplateException e) {
            throw new RuntimeException("Error generating Java file from template.", e);
        }
    }

    @Override
    public List<RootFileItem> bfsRootFile(String rootPath) {
        URL resourceURL = this.getClass().getClassLoader().getResource("templates");
        if (resourceURL == null) {
            throw new RuntimeException("Resource Dir not found");
        }
        log.info("resourceURL: {}", resourceURL);
        String externalForm = resourceURL.toExternalForm();
        log.info("externalForm: {}", externalForm);


        if ("jar".equals(resourceURL.getProtocol())) {
            // jar包遍历方式
            // 获取jar包全路径
            String jarPath = externalForm.substring(11, externalForm.indexOf("!") - 1);
            log.info("externalForm - linux: {}", jarPath);
            try (JarFile jarFile = new JarFile(jarPath)) {
                List<JarEntry> list = jarFile.stream()
                        .filter(entry -> entry.getName().contains("templates/" + rootPath))
                        .filter(entry -> !entry.isDirectory())
                        .toList();
                if (list.isEmpty()) {
                    throw new RuntimeException("Resource not found");
                }

                List<RootFileItem> fileItems = new ArrayList<>(list.size());
                for (JarEntry jarEntry : list) {
                    String useName = jarEntry.getName().substring("BOOT-INF/classes/templates/".length());
                    RootFileItem build = RootFileItem.builder()
                            .templatePath(useName)
                            .outputDir(useName.substring(0, useName.lastIndexOf("/") + 1))
                            .outputSuffix(useName.substring(useName.lastIndexOf("/") + 1,useName.length() - 4))
                            .build();
                    fileItems.add(build);
                    log.info("Item: {}", build);
                }
                return fileItems;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        } else {
            // 如果资源不在JAR包内，直接遍历文件系统
            externalForm = externalForm.substring(5);
            log.info("externalForm - windows: {}", externalForm);

            try {
                Path path = Paths.get(resourceURL.toURI());
                if (!Files.isDirectory(path)) {
                    throw new RuntimeException("Invalid root path");
                }

                List<RootFileItem> fileItems = new ArrayList<>();
                // 广搜队列
                LinkedList<File> queue = new LinkedList<>();
                queue.add(new File(externalForm + "/" + rootPath));
                // 循环检索
                while (!queue.isEmpty()) {
                    File first = queue.poll();
                    File[] files = first.listFiles();
                    if (files != null) {
                        for (File file : files) {
                            if (file.isDirectory()) {
                                queue.add(file);
                            } else {
                                String absolutePath = first.getPath();
                                RootFileItem build = RootFileItem.builder()
                                        .templatePath(file.getPath().substring(externalForm.length()))
                                        .outputDir(absolutePath.substring(externalForm.length()) + "/")
                                        .outputSuffix(file.getName().substring(0, file.getName().lastIndexOf(".")))
                                        .build();
                                fileItems.add(build);
                            }
                        }
                    }
                }
                return fileItems;
            } catch (Exception e) {
                throw new RuntimeException("Invalid resource URL", e);
            }
        }
    }

    @Override
    public Map<String, Object> getDataModel(Integer tableId) {
        HashMap<String, Object> outer = new HashMap<>();
        // TableModel
        AutoTable autoTable = autoTableService.getById(tableId);
        outer.put("TableName", autoTable.getTableName());
        outer.put("ClassName", autoTable.getClassName());
        outer.put("TableComment", autoTable.getTableComment());
        outer.put("Author", autoTable.getAuthor());
        outer.put("Email", autoTable.getEmail());
        outer.put("DateTime", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

        // FiledModel
        List<AutoField> fieldListByTableId = autoFieldService.list(Wrappers.lambdaQuery(AutoField.class)
                .eq(AutoField::getTableId, tableId));
        ArrayList<HashMap<String, Object>> filedList = new ArrayList<>(fieldListByTableId.size());
        outer.put("FiledList", filedList);
        fieldListByTableId.forEach(field -> {
            HashMap<String, Object> inner = new HashMap<>();
            inner.put("FieldName", field.getFieldName());
            inner.put("FieldComment", field.getFieldComment());
            inner.put("FieldType", field.getFieldType());
            inner.put("CaseName", field.getCaseName());
            inner.put("CaseType", field.getCaseType());
            inner.put("OrderNum", field.getOrderNum());
            inner.put("MainKey", field.getMainKey());
            filedList.add(inner);
        });
        filedList.sort(Comparator.comparingInt(o -> (Integer) o.get("OrderNum")));
        return outer;
    }

}
